/* 算法实现模板 */
import AlgorithmBase from "./AlgorithmBase";
import {mat_slove} from "../../../common/SVMFuncs.js";

import React from "react";
import ReactDom from 'react-dom';
import { loadDataset, txtToMatrix , mdRender} from "../../../common/Common";
import MatView2D, {createRandomPointStyleDict} from "../../../visual/MatView2D";
import Mat from "../../../common/Mat";

import 'katex/dist/katex.min.css';
import TDom from "../../../common/TDom";
import TCommon from "../../../common/TCommon";
import TMatrix from "../../../common/TMatrix";
import { showMathStringInTable } from "../../../visual/Table2D";

class NonlinearSVM extends AlgorithmBase{
  constructor(){
    super('NonlinearSVM');

    this.model = {C:0.1}

    this.descriptionUrl = `${this.homepage}/data/slm/ch7/7.4/README.md`;
    this.datasetUrl     = `${this.homepage}/data/slm/ch7/7.4/dataset.txt`;
    this.x = [];
    this.y = [];
    this.svm_s=[]
  }

  init(){ 
    this.reset();

    this.initTrainDS();
  }

  reset(){
    this.dataset = undefined;
    this.trainDS = undefined;
    this.model   = {C:1}

    this.clear();

    this.initDescription();
    this.updateModel();
  }
  
  // 模型UI
  initModelUI(){
    this.addLabel(this.domElementList.model, '惩罚参数:');
    this.etaInput = this.addFloatInput(
      this.domElementList.model, this.model.C, 
      (event)=>{
        const val = parseFloat(event.target.value);
        if(val<0){
          this.model.C = 0
          event.target.value = 0
        }
        else this.model.C = val;
        this.updateModel();
      });

    this.modelDiv = this.addDiv(this.domElementList.model);
  }

  // 渲染模型到页面
  updateModel(w, b){

    if(w === undefined) w = new Mat.zeros([1, 2]);
    if(b === undefined) b = 0;

    const modelString = String.raw
    `$$ \begin{aligned} w&:=[${w}] \\ b&:=${b} \\ C&:=${this.model.C} \\ K&(x)=x_i*y_i \end{aligned} $$`

    mdRender(`${modelString}`, this.modelDiv);

    // 显示数据集
    if(this.trainDS){
      ReactDom.unmountComponentAtNode(this.inputDiv);
      ReactDom.render(
        <React.StrictMode>
          <MatView2D 
            Mat     = {new TMatrix.Mat(this.trainDS.Mat)}
            Labels  = {this.trainDS.Labels}
            XRange  = {[0, 10]}
            YRange  = {[0, 10]}
            Separator = { {type:'line', w:w.toVector(), b:b} }
            PointStyleDict = {this.pointStyleDict}
          />
        </React.StrictMode>,
        this.inputDiv
      );
    }
  }

  updateOutput(w, b){

    if(w === undefined) w = new Mat.zeros([1, 2]);
    if(b === undefined) b = 0;

    const modelString = String.raw
    `$$ \begin{aligned} w&:=[${w}] \\ b&:=${b} \\ C&:=${this.model.C} \\ K&(x)=x_i*y_i \end{aligned} $$`

    mdRender(`${modelString}`, this.modelDiv);

    // 显示数据集
    if(this.trainDS){
      ReactDom.unmountComponentAtNode(this.outputDiv);
      ReactDom.render(
        <React.StrictMode>
          <MatView2D 
            Mat     = {new TMatrix.Mat(this.trainDS.Mat)}
            Labels  = {this.trainDS.Labels}
            XRange  = {[-5, 5]}
            YRange  = {[-5, 5]}
            Separator = { {type:'line', w:w.toVector(), b:b} }
            PointStyleDict = {this.pointStyleDict}
          />
        </React.StrictMode>,
        this.outputDiv
      );
    }
  }

  // 输入数据UI
  initInputUI(){

    // 文件选择器
    const fileSelector      = document.createElement('input');
    fileSelector.type       = 'file';
    fileSelector.hidden     = true;
    fileSelector.onchange   = this.handleFileChange.bind(this);   
    
    // this.addButton(this.domElementList.input, "获取服务器数据", this.initTrainDS.bind(this));
    TDom.createButton(this.domElementList.input, "获取服务器数据", this.initTrainDS.bind(this));
    this.addButton(this.domElementList.input, "使用本地数据", this.openFileSelector.bind(this));
    this.domElementList.input.appendChild(fileSelector);
    this.addButton(this.domElementList.input, "训练", this.train.bind(this));

    this.labelTrainTips = this.addLabel(this.domElementList.input, '');
    this.inputDiv       = this.addDiv(this.domElementList.input);

    this.fileSelector  = fileSelector;
  }

  // 输出UI
  initOutputUI(){
    this.outputDiv   = this.addDiv(this.domElementList.output);
    this.trainLogDiv = this.addDiv(this.domElementList.output);
  }

  // 获取描述md文件
  initDescription(){
    TDom.getDataFromUrl(this.descriptionUrl, (txt)=>{
      mdRender(txt, this.descriptionDiv);
    })

  }

  // 获取服务器数据集
  initTrainDS(){
    this.prepare(this.datasetUrl);
  }

  // 准备数据训练数据
  prepare(url){
    TDom.getDataFromUrl(url, 
      (txt)=>{
      this.dataset = txtToMatrix(txt);
      this.trainDS = TCommon.loadDataset(this.dataset);
      this.pointStyleDict = createRandomPointStyleDict(this.trainDS.Labels);
      this.updateModel();
    })
  }

  openFileSelector(){
    this.fileSelector.click();
  }
  // 获取文件路径加载
  handleFileChange(event){
    const files = event.target.files;

    for(let i = 0, il = files.length; i < il; i++){
      let reader = new FileReader();
      reader.onload = (e) => {      
        this.prepare(e.target.result);
        if(i === files.length - 1){
          event.target.value = '';
        }
      };
      reader.readAsDataURL(files[i])
    }
  }

  inner_product(x1=[],x2=x1){
    var res 
    if(x1.length==x2.length){
      res = 0
      for(var i=0;i<x1.length;i++){
        res+=x1[i]*x2[i]
      }
    }
    return res
  }

  svm_mat(x=this.x.slice(),y=this.y.slice()){
    var svm = []
    
    if(x[x.length-1]!='x'){
      var n = this.y.length-1
      this.yn = this.y[n]
      for(var i=0;i<y.length;i++){
        svm.push([])
        for(var j=0;j<y.length;j++){
          if(j==i){
            var res = parseFloat(this.inner_product(x[i]) + this.inner_product(this.x[n]) - 2*this.inner_product(this.x[n],x[i]))
            svm[i].push(res)
          }
          else{
            var res = parseFloat((this.inner_product(this.x[n]) + this.inner_product(x[i],x[j]) - this.inner_product(this.x[n],x[j]) - this.inner_product(this.x[n],x[i]))*y[i]*y[j])
            // var res = parseFloat((this.inner_product(this.x[n]) - this.inner_product(this.x[n],x[j]))*y[i]*y[j])
            svm[i].push(res)
          }
        }
        svm[i].push(1-y[i]*this.y[n])
      }
    }
    else{
      var n = this.y.length-1
      this.yn = this.y[n]
      var label_n = 0
      if(this.yn==1){
        for(var i=0;i<y.length-1;i++){
          if(y[i]==1){
            label_n = i
            break
          }
        }
      }
      else{
        for(var i=0;i<y.length-1;i++){
          if(y[i]==-1){
            label_n = i
            break
          }
        }
      }
      for(var i=0;i<y.length-1;i++){
        svm.push([])
        if(i!=label_n){
          for(var j=0;j<y.length-1;j++){
            if(j!=label_n){
              if(j==i){
                var res = parseFloat(this.inner_product(x[i]) + this.inner_product(x[label_n]) - 2*this.inner_product(x[label_n],x[i]))
                svm[i].push(res)
              }
              else{
                var res = parseFloat((this.inner_product(x[label_n]) + this.inner_product(x[i],x[j]) - this.inner_product(x[label_n],x[j]) - this.inner_product(x[label_n],x[i]))*y[i]*y[j])
                // console.log(svm[i]);
                // console.log(res);
                svm[i].push(res)
              }
            }
          }
          svm[i].push(1-y[i]*y[label_n])
        }    
      }
      svm.slice(0,label_n).concat(svm.slice(label_n+1))
    }

    return svm
  }

  slope(x1=[],x2=[]){
    var k = 0
    k = (x2[1] - x1[1])/(x2[0] - x1[0])
    
    return k
  }

  svm_slove(){
    var alpha_label = []
    this.alpha = []
    var n = this.y.length-1
    this.yn = this.y[n]

    for(var k=0;k<this.y.length-1;k++){
      alpha_label.push(k)
      this.alpha.push(0)
    }
    this.alpha_opt = []
    this.min = Infinity

    var pos_num = [],neg_num = []
    for(var i=0;i<alpha_label.length;i++){
      if(this.y[alpha_label[i]]==1){
        pos_num.push(alpha_label[i])
      }
      else{
        neg_num.push(alpha_label[i])
      }
    }
    var pos_num_ = pos_num.length
    var neg_num_ = neg_num.length
    if(this.yn==1) pos_num_++
    else neg_num_++

    var label_index = []

    if(this.yn==1){
      var pos_label_index = []
      var neg_label_index = []
      for(var i=pos_num_-1;i>0;i--){
        var index = []
        for(var j=0;j<i;j++) index.push(0)
        this.index = []
        this.create(pos_num_-1,i,pos_num,index)
        for(var k=0;k<this.index.length;k++) pos_label_index.push(this.index[k])
      }
      for(var i=neg_num_;i>0;i--){
        var index = []
        for(var j=0;j<i;j++) index.push(0)
        this.index = []
        this.create(neg_num_,i,neg_num,index)
        for(var k=0;k<this.index.length;k++) neg_label_index.push(this.index[k])
      }
      pos_label_index.push([])
      for(var i=0;i<pos_label_index.length;i++){
        for(var j=0;j<neg_label_index.length;j++){
          label_index.push(pos_label_index[i].concat(neg_label_index[j]))
        }
      }
      for(var i=0;i<label_index.length;i++) this.svm_slove_(label_index[i])
    }
    else{
      var pos_label_index = []
      var neg_label_index = []
      for(var i=pos_num_;i>0;i--){
        var index = []
        for(var j=0;j<i;j++) index.push(0)
        this.index = []
        this.create(pos_num_,i,pos_num,index)
        for(var k=0;k<this.index.length;k++) pos_label_index.push(this.index[k])
      }
      for(var i=neg_num_-1;i>0;i--){
        var index = []
        for(var j=0;j<i;j++) index.push(0)
        this.index = []
        this.create(neg_num_-1,i,neg_num,index)
        for(var k=0;k<this.index.length;k++) neg_label_index.push(this.index[k])
      }
      var label_index_ = []
      for(var i=0;i<neg_label_index.length;i++) label_index_.push(neg_label_index[i].concat(['x']))
      var neg_label_index_ = neg_label_index.concat(label_index_)
      neg_label_index = neg_label_index_.slice()
      neg_label_index.push([])

      for(var i=0;i<pos_label_index.length;i++){
        for(var j=0;j<neg_label_index.length;j++){
          label_index.push(pos_label_index[i].concat(neg_label_index[j]))
        }
      }
      for(var i=0;i<label_index.length;i++) this.svm_slove_(label_index[i])
    }

  }

  svm_slove_(alpha_label){
    if(this.slove==0){
      var x=[],y=[]    
      for(var i=0;i<alpha_label.length;i++){
        if(alpha_label[i]!='x'){  
          x.push(this.x[alpha_label[i]])
          y.push(this.y[alpha_label[i]])
        }
        else{
          x.push('x')
          y.push(this.yn)
        }
      }

      //new
      var pos_num = [],neg_num = []
      for(var i=0;i<alpha_label.length;i++){
        if(alpha_label[i]!='x'){  
          if(this.y[alpha_label[i]]==1){
            pos_num.push(alpha_label[i])
          }
          else{
            neg_num.push(alpha_label[i])
          }
        }
      }
      var pos_num_ = pos_num.length
      var neg_num_ = neg_num.length
      var is_x = 0
      if(alpha_label[alpha_label.length-1]!='x'){
        if(this.yn==1) pos_num_++
        else neg_num_++
      }
      else is_x = -1

      //判断是否需要解方程组
      var flag_slove = 0
      if(this.yn==1){
        var pos_k = 0,neg_k=0
        if(pos_num_>3+is_x){
          var pos_index = Math.floor(Math.random()*(pos_num_-3-is_x))+1
          if(Math.abs(this.slope(this.x[pos_num[0]],this.x[pos_num[pos_num_-2-is_x]])-this.slope(this.x[pos_num[0]],this.x[pos_num[pos_index]]))<=0.01) pos_k = (this.slope(this.x[pos_num[0]],this.x[pos_num[pos_num_-2-is_x]])).toFixed(2)
          else flag_slove=1
        }
        if(neg_num_>1){
          if(neg_num_>2){
            var neg_index = Math.floor(Math.random()*(neg_num_-2))+1
            if(Math.abs(this.slope(this.x[neg_num[0]],this.x[neg_num[neg_num_-1]])-this.slope(this.x[neg_num[0]],this.x[neg_num[neg_index]]))<=0.01) neg_k = (this.slope(this.x[neg_num[0]],this.x[neg_num[neg_num_-1]])).toFixed(2)
            else flag_slove=1
          }
          else neg_k = (this.slope(this.x[neg_num[0]],this.x[neg_num[1]])).toFixed(2)
        }
        if(pos_k!=0&&neg_k!=0&&Math.abs(pos_k-neg_k)>0.01) flag_slove=1
      }
      else{
        var pos_k = 0,neg_k=0
        if(neg_num_>3+is_x){
          var neg_index = Math.floor(Math.random()*(neg_num_-3-is_x))+1
          if(Math.abs(this.slope(this.x[neg_num[0]],this.x[neg_num[neg_num_-2-is_x]])-this.slope(this.x[neg_num[0]],this.x[neg_num[neg_index]]))<=0.01) neg_k = (this.slope(this.x[neg_num[0]],this.x[neg_num[neg_num_-2-is_x]])).toFixed(2)
          else flag_slove=1
        }
        if(pos_num_>1){
          if(pos_num_>2){
            var pos_index = Math.floor(Math.random()*(pos_num_-2))+1
            if(Math.abs(this.slope(this.x[pos_num[0]],this.x[pos_num[pos_num_-1]])-this.slope(this.x[pos_num[0]],this.x[pos_num[pos_index]]))<=0.01) pos_k = (this.slope(this.x[pos_num[0]],this.x[pos_num[pos_num_-1]])).toFixed(2)
            else flag_slove=1
          }
          else pos_k = (this.slope(this.x[pos_num[0]],this.x[pos_num[1]])).toFixed(2)
        }
        if(pos_k!=0&&neg_k!=0&&Math.abs(pos_k-neg_k)>0.01) flag_slove=1
      }

      if(flag_slove==0){
        var svm = this.svm_mat(x,y)
        var s = mat_slove(svm.slice())

        var zero_num = 0
        
        if(s == undefined){
          this.count++
          this.trainLog.push([`${this.count}`,`无解`,`---`,`---`,`${this.min}`])
        }
        else{
          if(x[x.length-1]=='x'){
            var label_n = 0
            for(var ii=0;ii<y.length-1;ii++){
              if(y[ii]==y[y.length-1]){
                label_n = ii
                break
              }
            }
            var a_new = 0
            for(var jj=0;jj<y.length-1;jj++){
              if(jj!=label_n){
                a_new += -s[jj]*y[jj]
              }
            }
            a_new = a_new*y[label_n]
            var s_ = s.slice()
            s = s_.slice(0,label_n).concat([a_new]).concat(s_.slice(label_n))
            // console.log(x);
            // console.log(s);
          }
          
          for(var i=0;i<s.length;i++){
            if(s[i]==0) zero_num++
          }

          this.count++
          this.alpha = []
          for(var i=0;i<this.y.length;i++){
            this.alpha.push(0)
          }

          var flag = 0

          for(var j=0;j<s.length;j++){
            if(!(s[j]>=0)){
              flag = 1
            }
            this.alpha[alpha_label[j]] = parseFloat(s[j])
          }
          for(var a=0;a<this.alpha.length-1;a++){
            this.alpha[this.alpha.length-1]+=this.y[a]*this.alpha[a]
          }
          if(!(this.alpha[this.alpha.length-1]>=0)) flag = 1

          var alpha_log = []
          for(var k=0;k<this.alpha.length;k++){
            alpha_log.push(`\\alpha_{${k+1}}`+`=${this.alpha[k]}`)
          }

          if(flag!=1 && zero_num!=s.length){
            var w_1 = 0
            var w_2 = 0
            var b = 0
            var alpha_j
            for(var j=0;j<this.alpha.length;j++){
              if(this.alpha[j]>0){
                alpha_j = j
                break
              }
            }
            for(var i=0;i<this.y.length;i++){
              w_1 += this.alpha[i]*this.y[i]*this.x[i][0]
              w_2 += this.alpha[i]*this.y[i]*this.x[i][1]
              b += this.alpha[i]*this.y[i]*this.inner_product(this.x[i],this.x[alpha_j])
            }
            b = this.y[alpha_j] - b
            
            var l1 = 0
            var l2 = 0
            for(var i=0;i<this.alpha.length;i++){
              for(var j=0;j<this.alpha.length;j++){
                l1+=parseFloat(this.alpha[i]*this.alpha[j]*this.y[i]*this.y[j]*this.inner_product(this.x[i],this.x[j]))/2
              }
              l2+=this.alpha[i]
            }
            var l = l1-l2

            if(l<this.min){
              this.min = l
              this.alpha_opt = this.alpha.slice()
              
            }
            this.trainLog.push([`${this.count}`,`${alpha_log}`,`局部最优解!`,`${l}`,`${this.min}`])
            
          }
          else{
            this.trainLog.push([`${this.count}`,`${alpha_log}`,`非最优!`,`---`,`${this.min}`])
          }      
        }
      }
    // else console.log(alpha_label);
  }
  }

  create(n=0,m=0,x=[],index=[]){
    for(var i=m;i<=n;i++){
      index[m-1] = i-1
      
      if(m>1) this.create(i-1,m-1,x,index)
      else{
        var list = []
        for(var j=0;j<index.length;j++) list.push(x[index[j]])
        this.index.push(list)
      }
    }
  }

  kernel(){
    var mat = this.trainDS.Mat.slice(),label = this.trainDS.Labels.slice()
    
    for(var i=0;i<mat.length;i++){
      mat[i] = [parseFloat(mat[i][0])*parseFloat(label[i]),parseFloat(mat[i][1])*parseFloat(label[i])]
    }
    this.trainDS.Mat = mat.slice()
  }

  // 训练
  train(){
    this.kernel();
    console.log(this.trainDS);
    const trainDS = this.trainDS;
    if(!trainDS) return;

    let X = new Mat.Mat(trainDS.Mat);
    let y = new Mat.Mat(trainDS.Labels);
    var w = new Mat.zeros([1, 2]);
    var b = 0.0;

    for(var i=0;i<y.length;i++){
      y[i]=y[i][0]
    }

    var x=[]
    for(var i=0;i<X.length;i++){
      x.push(X[i])
    }

    this.x = x
    this.y = y

    this.trainLog = []
    this.count = 0
    this.trainLog.push(["迭代次数",'\alpha的值',"是否最优","目标函数值","目标函数最小值"])
    this.slove=0
    this.svm_slove(x,y)
    var w_1 = 0
    var w_2 = 0
    var b_ = 0
    var alpha_j
    for(var j=0;j<this.alpha_opt.length;j++){
      if(this.alpha_opt[j]>0){
        alpha_j = j
        break
      }
    }
    for(var i=0;i<this.y.length;i++){
      w_1 += this.alpha_opt[i]*this.y[i]*this.x[i][0]
      w_2 += this.alpha_opt[i]*this.y[i]*this.x[i][1]
      b_ += this.alpha_opt[i]*this.y[i]*this.inner_product(this.x[i],this.x[alpha_j])
    }
    b_ = this.y[alpha_j] - b_
    w[0][0] = w_1;
    w[0][1] = w_2;
    b = b_

    this.updateOutput(w, b);
    this.labelTrainTips.innerText = '训练完毕';
    if(this.trainLogDiv) this.trainLogDiv.innerHTML = '';
    showMathStringInTable(this.trainLogDiv, this.trainLog);
                
  }
}


export default NonlinearSVM;